'''
@create_time: 2025/10/17 下午5:49
@Author: GeChao
@File: page_object.py
'''
import re

'''
ElementFactory 元素定位于等待统一工厂
ElementWrapper 封装单个元素操作（点击、输入、取值）
PageElement 页面类中定义元素
'''

import asyncio
from typing import Optional, Union, Any, Coroutine
from tools.log import Logger
from playwright.async_api import Page, Locator, TimeoutError as PlaywrightTimeoutError

logger = Logger().logger


class ElementFactory:
    """统一的元素工厂，集成定位和等待功能"""

    # 定位类型映射（Playwright使用不同的选择器语法）
    _LOCATOR_MAPPING = {
        "css": lambda value: value,  # CSS选择器
        "xpath": lambda value: value,  # XPath
        "id": lambda value: f"#{value}",  # ID
        "class": lambda value: f".{value}",  # 类名
        "name": lambda value: f"[name='{value}']",  # name属性
        "text": lambda value: f"text='{value}'",  # 文本匹配
    }

    # 等待状态映射
    _WAIT_CONDITIONS = {
        "presence_of_element_located": "attached",
        "element_to_be_clickable": "visible",
        "visibility_of": "visible",
        "invisible": "hidden",
        "element_located_to_be_selected": "visible",
    }

    @classmethod
    def get_selector(cls, locator_type: str, locator_value: str) -> str:
        """获取Playwright选择器"""
        selector_func = cls._LOCATOR_MAPPING.get(locator_type)
        if not selector_func:
            raise ValueError(f"不支持的定位类型: {locator_type}")
        return selector_func(locator_value)

    @classmethod
    def get_wait_state(cls, wait_type: str) -> str:
        """获取等待状态"""
        return cls._WAIT_CONDITIONS.get(wait_type, "visible")

    @classmethod
    async def find_element(
            cls,
            page: Page,
            locator_type: str,
            locator_value: str,
            wait_type: str = "presence_of_element_located",
            timeout: int = 10,
            highlight: bool = False,
            highlight_duration: int = 5
    ) -> Locator:
        """查找元素，并应用等待条件（优化版 - 移除未使用的特殊等待条件）"""
        # 获取选择器和等待状态
        selector = cls.get_selector(locator_type, locator_value)
        state = cls.get_wait_state(wait_type)

        # 创建定位器并等待
        locator = page.locator(selector)
        await locator.wait_for(state=state, timeout=timeout * 1000)

        # 高亮显示(如果需要)
        if highlight:
            await cls.highlight_element(page, locator, highlight_duration)

        return locator

    @staticmethod
    async def highlight_element(page: Page, locator: Locator, duration: int = 5) -> None:
        """高亮显示元素"""
        element = await locator.element_handle()
        await page.evaluate(
            """
            ([element, duration]) => {
                const original = element.getAttribute('style') || '';
                element.setAttribute('style', original + '; border: 3px dashed rgb(250,0,255);');
                setTimeout(() => {
                    element.setAttribute('style', original);
                }, duration * 1000);
            }
            """,
            [element, duration]
        )


class ElementWrapper:
    """元素包装器类 - 封装常用元素操作（优化版 - 只保留实际使用的方法）"""

    def __init__(self, locator: Locator, page: Page, desc: str = ""):
        self.locator = locator
        self.page = page
        self.desc = desc

    async def click(self, **kwargs):
        """点击元素（失败时自动尝试强制点击）"""
        logger.info(f"元素点击: {self.desc}")
        try:
            await self.locator.click(**kwargs)
        except Exception:
            logger.warning(f'{self.desc}元素点击失败，尝试强制点击')
            try:
                await self.locator.click(force=True)
            except Exception as e:
                logger.error(f'{self.desc}强制点击也失败: {e}')
                raise e

    async def fill(self, value: str, **kwargs):
        """填充元素内容"""
        logger.info(f"元素填充内容: {self.desc}, value: {value}")
        await self.locator.fill(str(value), **kwargs)
        return self

    async def is_visible(self) -> bool:
        """判断元素是否可见"""
        # 注意：is_visible() 是异步方法，需要 await
        logger.info(f"元素是否可见: {self.desc}")
        return await self.locator.is_visible()

    def text_filter(self, text):
        """过滤元素"""
        logger.info(f"元素过滤: {self.desc}, text: {text}")
        return self.locator.filter(has_text=text).first

    def nth(self, index: int):
        """获取指定索引的元素"""
        logger.info(f"获取指定索引的元素: {self.desc}, index: {index}")
        locator = self.locator.nth(index)
        return ElementWrapper(locator, self.page, self.desc)

    async def text_content(self, timeout: int = 5000):
        """获取元素的文本内容"""
        # logger.info(f"文本内容: {self.desc}")
        text = await self.locator.text_content(timeout=timeout)
        logger.info(f"获取到的文本内容: {text}")
        return text
        # return self.locator.text_content()


class PageElement:
    """统一的页面元素工厂类 - 支持静态和动态元素定位"""

    def __init__(
            self,
            locator_type: Optional[str] = None,
            locator_value: Optional[str] = None,
            dynamic: bool = False,
            wait_type: str = "presence_of_element_located",
            timeout: int = 20,
            describe: str = "undefined",
            mark: bool = True,
            delay: Union[int, float, bool] = False,
            **kwargs
    ):
        """初始化页面元素，支持静态和动态两种模式"""
        self.timeout = timeout
        self.desc = describe
        self.mark = mark
        self.delay = delay
        self.wait_type = wait_type
        self.dynamic = dynamic

        # 处理定位方式和定位值
        if kwargs:
            # 旧方式: 从kwargs中提取定位类型和值（兼容性）
            for key, value in kwargs.items():
                if key in ElementFactory._LOCATOR_MAPPING.keys():
                    self.locator_type = key
                    self.locator_value = value
                    break
            else:
                raise ValueError(f"未找到支持的定位类型，请使用: {', '.join(ElementFactory._LOCATOR_MAPPING.keys())}")
        elif locator_type:
            self.locator_type = locator_type
            self.locator_value = locator_value if not dynamic else None
        else:
            raise ValueError("请提供定位类型和定位值，或使用关键字参数形式")

    def __get__(self, instance, owner):
        """描述符__get__方法，获取元素"""
        if instance is None:
            return self

        # 动态元素 - 返回可调用对象
        if self.dynamic:
            # 返回一个包装函数，避免直接返回协程
            async def get_dynamic_element(**kwargs):
                locator = await self._create_dynamic_element(instance, **kwargs)
                return locator
            return get_dynamic_element

        # 静态元素 - 返回协程对象
        return self._create_static_element(instance)

    async def _create_static_element(self, instance):
        """创建静态元素"""
        page = instance.page
        # 应用延迟
        if self.delay:
            delay_time = self.delay if isinstance(self.delay, (int, float)) else 1
            logger.info(f"{self.desc}延迟执行{delay_time}秒")
            await asyncio.sleep(delay_time)

        try:
            # 使用统一的元素工厂进行定位和等待
            locator = await ElementFactory.find_element(
                page=page,
                locator_type=self.locator_type,
                locator_value=self.locator_value,
                wait_type=self.wait_type,
                timeout=self.timeout,
                highlight=self.mark
            )
            return ElementWrapper(locator, page, self.desc)
        except PlaywrightTimeoutError as e:
            logger.error(f'{self.desc} 发生 TimeoutError，等待元素超时: {e}')
            raise PlaywrightTimeoutError(f'{self.desc} 等待元素超时: {e}')
        except Exception as e:
            logger.error(f'{self.desc} 发生未知异常: {e}')
            raise Exception(f'{self.desc} 未知异常: {e}')

    async def _create_dynamic_locator(self, instance):
        """创建动态定位器(返回可调用对象)"""

        class DynamicLocator:
            def __init__(self, page_element, page_instance):
                self.page_element = page_element
                self.page_instance = page_instance

            def __call__(self, **kwargs):
                return self.page_element._create_dynamic_element(self.page_instance, **kwargs)

        return DynamicLocator(self, instance)

    async def _create_dynamic_element(self, instance, **kwargs):
        """根据动态参数创建元素"""
        page = instance.page

        # 根据定位类型选择 Playwright 定位方法
        if self.locator_type == "role":
            role = kwargs.get("role")
            name = kwargs.get("name")
            locator = page.get_by_role(role, name=name)
        elif self.locator_type in ["text", "textbox"]:
            text = kwargs.get("text") or kwargs.get("textbox")
            exact = kwargs.get("exact", True)
            locator = page.get_by_text(text, exact=exact)
        elif self.locator_type == "placeholder":
            placeholder = kwargs.get("placeholder")
            locator = page.get_by_placeholder(placeholder)
        elif self.locator_type == "label":
            label = kwargs.get("label")
            locator = page.get_by_label(label)
        elif self.locator_type == "title":
            title = kwargs.get("title")
            exact = kwargs.get("exact", True)
            locator = page.get_by_title(title, exact=exact)
        elif self.locator_type == "text_sear_click":
            text = kwargs.get("text")
            pattern = re.compile(rf"^{text}$")
            locator = page.locator("span").filter(has_text=pattern)
        elif self.locator_type == "title_with_div":
            title = kwargs.get("title")
            locator = page.get_by_title(title).locator("div").first
        # elif self.locator_type == "locator":
        #     row_index = kwargs.get("row", 0)
        #     column_index = kwargs.get("column")
        #     # 优先尝试通过 data-index 定位行
        #     locator = page.locator(f"tr[data-index='{row_index}'] .ant-table-cell").nth(column_index)
        elif self.locator_type == "locator":
            row_index = kwargs.get("row", 0)  # 行索引（从0开始）
            column_index = kwargs.get("column")  # 列索引（从0开始，需按DOM结构确认）

            # 1. 优先尝试通过 data-index 定位行（适用于第一张图表格）
            row_locator = page.locator(f"tr[data-index='{row_index}']")

            # 2. 若 data-index 不存在（如第二张图表格），则通过行顺序（nth）定位
            if await row_locator.count() == 0:  # 检查是否存在该元素
                # 定位表格 tbody 下的所有行，按索引取第 row_index 行
                # （.ant-table-row 是 AntD 表格行的常见类名，若没有可改用 "tbody > tr"）
                row_locator = page.locator(".ant-table-row").nth(row_index)

            # 3. 定位该行下的第 column_index 列单元格
            locator = row_locator.locator(".ant-table-cell").nth(column_index)

            # 4. 等待元素加载完成（增强稳定性，可选但推荐）
            await locator.wait_for(state="visible", timeout=5000)
        else:
            raise ValueError(f"动态模式不支持的定位类型: {self.locator_type}")

        return ElementWrapper(locator, page, f"{self.desc}[{kwargs}]")

    # @staticmethod
    # async def hover_and_get_popup_text(page, row=0, column=8):
    #     cell = page.locator(f"tr[data-index='{row}'] .ant-table-cell").nth(column)
    #     await cell.hover()
    #     popup = page.locator(".ant-popover-content, .ant-tooltip-content")
    #     await popup.wait_for(state="visible")
    #     return await popup.text_content()
